package cn.itcast.nems.product.scheduler;

import cn.hutool.core.lang.Assert;
import cn.itcast.ic.common.exception.v2.BizExceptionProducer;
import cn.itcast.nems.base.dao.dataobject.ClazzDO;
import cn.itcast.nems.base.dao.mapper.ClazzMapper;
import cn.itcast.nems.common.account.AccountHolder;
import cn.itcast.nems.common.bean.ProductState;
import cn.itcast.nems.common.constant.PaymentTypeEum;
import cn.itcast.nems.common.tenant.TenantInfo;
import cn.itcast.nems.product.api.TuitionFeeApi;
import cn.itcast.nems.product.dao.dataobject.BizNo;
import cn.itcast.nems.product.dao.dataobject.ProductDO;
import cn.itcast.nems.product.dao.dataobject.ProductPriceRuleDO;
import cn.itcast.nems.product.dao.dataobject.TuitionFeeDO;
import cn.itcast.nems.product.dao.entity.ProductPriceUpdateTask;
import cn.itcast.nems.product.dao.mapper.ProductPriceUpdateTaskMapper;
import cn.itcast.nems.product.dao.mapper.TuitionFeeMapper;
import cn.itcast.nems.product.dto.TuitionFeeDTO;
import cn.itcast.nems.product.service.ProductPriceRuleService;
import cn.itcast.nems.product.service.ProductService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Set;

import static cn.itcast.nems.studentrecord.enumeration.StudentRecordErrorBodyEnum.REQUEST_PARAM_CLAZZ_NO_EXIST;
import static cn.itcast.nems.studentrecord.enumeration.StudentRecordErrorBodyEnum.REQUEST_PARAM_TUITION_NO_EXIST;

/**
 * @author liyong
 * create 2023-08-30 16:07:17
 **/
@Service
public class TuitionFeeService implements TuitionFeeApi {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    private final TuitionFeeMapper tuitionFeeMapper;
    private final ProductPriceUpdateTaskMapper productPriceUpdateTaskMapper;
    private final ProductService productService;
    private final ProductPriceRuleService productPriceRuleService;

    private final ClazzMapper clazzMapper;

    public TuitionFeeService(TuitionFeeMapper tuitionFeeMapper,
                             ProductPriceUpdateTaskMapper productPriceUpdateTaskMapper,
                             ProductService productService,
                             ProductPriceRuleService productPriceRuleService, ClazzMapper clazzMapper) {
        this.tuitionFeeMapper = tuitionFeeMapper;
        this.productPriceUpdateTaskMapper = productPriceUpdateTaskMapper;
        this.productService = productService;
        this.productPriceRuleService = productPriceRuleService;
        this.clazzMapper = clazzMapper;
    }

    /**
     * 刷新价格信息
     */
    public void flushProductPrice(boolean flushAll, int clazzId) {
        List<ProductDO> products;
        if (clazzId > 0) {
            products = this.productService.findProducts(clazzId);
        } else {
            products = this.findProducts(flushAll);
        }
        this.flushProductPrice(products, AccountHolder.getRequiredAccount(), null);
    }

    @Transactional
    public void flushProductPrice(List<ProductDO> products, String operator, Set<String> classTypeIds) {
        long initStart = System.currentTimeMillis();
        long start = initStart;
        BaseDataProcessor baseDataProcessor;
        if (classTypeIds == null || classTypeIds.isEmpty()) {
            baseDataProcessor = new BaseDataProcessor(this.tuitionFeeMapper);
        } else {
            baseDataProcessor = new BaseDataProcessor(this.tuitionFeeMapper, classTypeIds);
        }
        long end = System.currentTimeMillis();
        logger.info("读取价格、阶段、校区数据耗时 {} 秒", (end - start) / 1000);

        start = end;
        PriceChangeProductProcessor productProcessor = new PriceChangeProductProcessor(productService, baseDataProcessor, products, operator);
        end = System.currentTimeMillis();
        logger.info("更新产品价格 数量 {} 耗时 {} 秒", productProcessor.getProcessCount(), (end - start) / 1000);

        start = end;
        ProductPriceRuleProcessor ruleProcessor = new ProductPriceRuleProcessor(productPriceRuleService, baseDataProcessor, products, operator);
        end = System.currentTimeMillis();
        logger.info("更新产品价格规则 数量 {} 耗时 {} 秒", ruleProcessor.getProcessCount(), (end - start) / 1000);

        this.addTaskLog(productProcessor, ruleProcessor, operator);
        end = System.currentTimeMillis();
        logger.info("刷新产品价格共耗时 {} 秒  数量 {}", (end - initStart) / 1000, products.size());
    }

    private void addTaskLog(PriceChangeProductProcessor productProcessor, ProductPriceRuleProcessor ruleProcessor, String operator) {
        ProductPriceUpdateTask task = new ProductPriceUpdateTask();
        task.setPriceAddQuantity(0);
        task.setPriceUpdateQuantity(productProcessor.getSuccessCount());
        task.setPriceFailQuantity(productProcessor.getResult().size() - productProcessor.getSuccessCount());
        task.setPriceRuleAddQuantity(0);
        task.setPriceRuleUpdateQuantity(ruleProcessor.getResult().size());
        task.setPriceRuleFailQuantity(0);
        task.setCreatedBy(operator);
        task.setCreatedDatetime(LocalDateTime.now());
        task.setTenantId(TenantInfo.DEFAULT_TENANT_ID);
        task.setSubtenantId(TenantInfo.DEFAULT_SUBTENANT_ID);
        this.productPriceUpdateTaskMapper.insert(task);
    }

    private List<ProductDO> findProducts(boolean flushAll) {
        LocalDate startDate = LocalDate.of(2023, 1, 1);
        // 去除待定班
        // LocalDate endDate = LocalDate.of(2999, 12, 31);
        ProductState state = null; // flushAll ? null : ProductState.VALID;
        Boolean includeExpired = flushAll;
        return this.productService.findProducts(startDate, null, state, includeExpired);
    }

    @Override
    public int findTuitionFee(Integer clazzId, String paymentType) {
        final BigDecimal tuition = this.findTuition(clazzId, paymentType);
        return tuition.multiply(BigDecimal.valueOf(100)).intValue();
    }

    public BigDecimal findTuition(Integer clazzId, String paymentType) {
        ClazzDO clazzDO = clazzMapper.findById(clazzId);
        Assert.notNull(clazzDO, BizExceptionProducer.produce(REQUEST_PARAM_CLAZZ_NO_EXIST, clazzId));
        LocalDate startDate = clazzDO.getStartDate();
        final String bizNo = BizNo.calBizNo(clazzId);
        final ProductPriceRuleDO priceRule = this.productPriceRuleService.findPriceRuleByBizNo(bizNo, startDate.atStartOfDay());
        Assert.notNull(priceRule, BizExceptionProducer.produce(REQUEST_PARAM_TUITION_NO_EXIST, clazzId));
        // 默认先付款
        Integer price = priceRule.getPrice();
        if (StringUtils.hasText(paymentType)) {
            if (PaymentTypeEum.LASTPAYMENT.name().equalsIgnoreCase(paymentType)) {
                price = priceRule.getPostpaidPrice();
            }
        }
        return BigDecimal.valueOf(price).divide(BigDecimal.valueOf(100), RoundingMode.HALF_UP);
    }

    private BigDecimal findTuition0(Integer clazzId, String paymentType) {
        TuitionFeeDO tuitionFee = this.findTuitionFee(clazzId, (LocalDate) null);
        // 默认先付款
        BigDecimal fee = tuitionFee.getFirst();
        if (StringUtils.hasText(paymentType)) {
            if (PaymentTypeEum.LASTPAYMENT.name().equalsIgnoreCase(paymentType)) {
                fee = tuitionFee.getLast();
            }
        }
        return fee;
    }

    @Override
    public TuitionFeeDTO findTuition(Integer clazzId){
        final TuitionFeeDO tuitionFee = this.findTuitionFee(clazzId, LocalDate.now());
        return new TuitionFeeDTO(tuitionFee.getId(), tuitionFee.getFirst(), tuitionFee.getLast());
    }

    private TuitionFeeDO findTuitionFee(Integer clazzId, LocalDate useDate) {
        ClazzDO clazzDO = clazzMapper.findById(clazzId);
        Assert.notNull(clazzDO, BizExceptionProducer.produce(REQUEST_PARAM_CLAZZ_NO_EXIST, clazzId));
        LocalDate startDate = useDate == null ? clazzDO.getStartDate() : useDate;
        TuitionFeeDO tuitionFeeDO = new TuitionFeeDO(clazzDO.getSchoolId(), clazzDO.getClassTypeId(), clazzDO.getPhaseNumber(), null, startDate);
        TuitionFeeDO tuitionFee = tuitionFeeMapper.findByParam(tuitionFeeDO);
        Assert.notNull(tuitionFee, BizExceptionProducer.produce(REQUEST_PARAM_TUITION_NO_EXIST, clazzId));
        return tuitionFee;
    }
}
